/*!	 synfig/rendering/opengl/task/taskcontourgl.cpp
**	 TaskContourGL
**
**	......... ... 2015 Ivan Mahonin
**
**	This package is free software; you can redistribute it and/or
**	modify it under the terms of the GNU General Public License as
**	published by the Free Software Foundation; either version 2 of
**	the License, or (at your option) any later version.
**
**	This package is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**	General Public License for more details.
**
*/

#ifdef USING_PCH
#	include "pch.h"
#else
#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#ifndef _WIN32
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#endif

#include "taskcontourgl.h"

#include "../internal/environment.h"
#include "../surfacegl.h"

#endif

using namespace synfig;
using namespace rendering;

void
TaskContourGL::render_polygon(
    const std::vector<Vector> &polygon,
    const Rect &bounds,
    bool invert,
    bool antialias,
    Contour::WindingStyle winding_style,
    const Color &color)
{
    if (polygon.empty()) {
        return;
    }

    gl::Environment &e = gl::Environment::get_instance();

    if (antialias) {
        e.antialiasing.multisample_begin(false);
    }

    glClearColor(color.get_r(), color.get_g(), color.get_b(), 0.f);
    glClear(GL_COLOR_BUFFER_BIT);
    glClearColor(0.f, 0.f, 0.f, 0.f);

    gl::Buffers::BufferLock quad_buf = e.buffers.get_default_quad_buffer();
    gl::Buffers::VertexArrayLock quad_va = e.buffers.get_vertex_array();

    gl::Buffers::BufferLock polygon_buf = e.buffers.get_array_buffer(polygon);
    gl::Buffers::VertexArrayLock polygon_va = e.buffers.get_vertex_array();

    bool even_odd = winding_style == Contour::WINDING_EVEN_ODD;

    GLint vp[4] = { };
    glGetIntegerv(GL_VIEWPORT, vp);
    int x0 = (int)floor(vp[0] + (bounds.minx + 1.0) * 0.5 * vp[2]);
    int x1 = (int)ceil(vp[0] + (bounds.maxx + 1.0) * 0.5 * vp[2]);
    int y0 = (int)floor(vp[1] + (bounds.miny + 1.0) * 0.5 * vp[3]);
    int y1 = (int)ceil(vp[1] + (bounds.maxy + 1.0) * 0.5 * vp[3]);
    glScissor(x0, y0, x1 - x0, y1 - y0);

    glEnable(GL_SCISSOR_TEST);
    glEnable(GL_STENCIL_TEST);

    // render mask

    glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
    glClear(GL_STENCIL_BUFFER_BIT);
    glStencilFunc(GL_ALWAYS, 0, 0);

    if (even_odd) {
        glStencilOp(GL_INCR_WRAP, GL_INCR_WRAP, GL_INCR_WRAP);
    } else {
        glStencilOpSeparate(GL_FRONT, GL_INCR_WRAP, GL_INCR_WRAP, GL_INCR_WRAP);
        glStencilOpSeparate(GL_BACK, GL_DECR_WRAP, GL_DECR_WRAP, GL_DECR_WRAP);
    }

    glBindVertexArray(polygon_va.get_id());
    glEnableVertexAttribArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, polygon_buf.get_id());
    glVertexAttribPointer(0, 2, GL_DOUBLE, GL_TRUE, 0, polygon_buf.get_pointer());
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    e.shaders.simple();
    glDrawArrays(GL_TRIANGLE_FAN, 0, polygon.size());

    glDisableVertexAttribArray(0);
    glBindVertexArray(0);

    // fill mask

    glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

    if (!even_odd && !invert) {
        glStencilFunc(GL_NOTEQUAL, 0, -1);
    }

    if (!even_odd &&  invert) {
        glStencilFunc(GL_EQUAL, 0, -1);
    }

    if (even_odd && !invert) {
        glStencilFunc(GL_EQUAL, 1, 1);
    }

    if (even_odd &&  invert) {
        glStencilFunc(GL_EQUAL, 0, 1);
    }

    glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE);

    glBindVertexArray(quad_va.get_id());
    glEnableVertexAttribArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, quad_buf.get_id());
    glVertexAttribPointer(0, 2, GL_DOUBLE, GL_TRUE, 0, quad_buf.get_pointer());
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    e.shaders.color(color);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    glDisableVertexAttribArray(0);
    glBindVertexArray(0);

    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
    glDisable(GL_STENCIL_TEST);
    glDisable(GL_SCISSOR_TEST);

    if (antialias) {
        e.antialiasing.multisample_end();
    }
}

void
TaskContourGL::render_contour(
    const Contour &contour,
    const Matrix &transform_matrix,
    bool invert,
    bool antialias,
    Contour::WindingStyle winding_style,
    const Color &color,
    Real detail)
{
    GLint vp[4] = { };
    glGetIntegerv(GL_VIEWPORT, vp);

    if (!vp[2] || !vp[3]) {
        return;
    }

    std::vector<Vector> polygon;
    Rect full_bounds(-1.0, -1.0, 1.0, 1.0);
    Rect bounds = full_bounds;
    Vector pixel_size(2.0 / (Real)vp[2], 2.0 / (Real)vp[3]);

    contour.split(polygon, bounds, transform_matrix, pixel_size * detail);

    if (invert) {
        bounds = full_bounds;
    }

    render_polygon(polygon, bounds, invert, antialias, winding_style, color);
}

bool
TaskContourGL::run(RunParams & /* params */) const
{
    gl::Context::Lock lock(env().context);

    SurfaceGL::Handle target =
        SurfaceGL::Handle::cast_dynamic(target_surface);

    // transformation

    Vector rect_size = get_source_rect_rb() - get_source_rect_lt();
    Matrix bounds_transfromation;
    bounds_transfromation.m00 = fabs(rect_size[0]) > 1e-10 ? 2.0 / rect_size[0] : 0.0;
    bounds_transfromation.m11 = fabs(rect_size[1]) > 1e-10 ? 2.0 / rect_size[1] : 0.0;
    bounds_transfromation.m20 = -1.0 - get_source_rect_lt()[0] * bounds_transfromation.m00;
    bounds_transfromation.m21 = -1.0 - get_source_rect_lt()[1] * bounds_transfromation.m11;

    Matrix matrix = transformation * bounds_transfromation;

    if (valid_target()) {
        // check bounds
        std::vector<Vector> polygon;
        Rect bounds(-1.0, -1.0, 1.0, 1.0);
        Vector pixel_size(
            2.0 / (Real)(get_target_rect().maxx - get_target_rect().minx),
            2.0 / (Real)(get_target_rect().maxy - get_target_rect().miny));
        contour->split(polygon, bounds, matrix, pixel_size * detail);

        // bind framebuffer

        gl::Framebuffers::RenderbufferLock renderbuffer = env().framebuffers.get_renderbuffer(GL_STENCIL_INDEX8, target->get_width(), target->get_height());
        gl::Framebuffers::FramebufferLock framebuffer = env().framebuffers.get_framebuffer();
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer.get_id());
        glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, renderbuffer.get_id());
        glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, target->get_id(), 0);
        env().framebuffers.check("TaskContourGL::run bind framebuffer");
        glViewport(
            get_target_rect().minx,
            get_target_rect().miny,
            get_target_rect().maxx - get_target_rect().minx,
            get_target_rect().maxy - get_target_rect().miny);
        env().context.check("TaskContourGL::run viewport");

        // render

        render_polygon(
            polygon,
            bounds,
            contour->invert,
            allow_antialias && contour->antialias,
            contour->winding_style,
            contour->color);

        // release framebuffer

        glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
        glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
        env().context.check("TaskContourGL::run release contour");
    }

    return true;
}